using System;
using SMS.SMPPClient.Util;
using SMS.SMPPClient.PDU;

namespace SMS.SMPPClient.PDU.Incoming
{
    /// <summary>
    /// Represents a response PDU from the SMSC.
    /// </summary>
    public abstract class SMPPResponse : PDU
    {
        private uint _CommandStatus;
        private TLVTable _TLVTable;
        private byte[] _Response;
        private uint _CommandLength;
        private uint _CommandID;
        private uint _SequenceNumber;

        /// <summary>
        /// Standard length of PDU header.
        /// </summary>
        protected const int HEADER_LENGTH = 16;

        /// <summary>
        /// The Tag, Length, Value table for this request.
        /// </summary>
        protected TLVTable tlvTable
        {

            get
            {
                return _TLVTable;
            }
        }

        /// <summary>
        /// Subclass accessor for the command ID.
        /// </summary>
        public CommandIDType CommandID
        {
            get
            {
                return (CommandIDType)Enum.Parse(typeof(CommandIDType),
                                                     Enum.GetName(typeof(CommandIDType), _CommandID));
            }
        }

        /// <summary>
        /// Subclass accessor for the command ID.
        /// </summary>
        public uint SequenceNumber
        {
            get
            {
                return _SequenceNumber;
            }
        }
        /// <summary>
        /// Defines the overall length of the PDU in octets (i.e. bytes).
        /// </summary>
        public uint CommandLength
        {
            get
            {
                return _CommandLength;
            }
        }

        /// <summary>
        /// The command status of this SMPP response.
        /// </summary>
        public uint CommandStatus
        {
            get
            {
                return _CommandStatus;
            }
        }

        /// <summary>
        /// What remains after the header is stripped off the PDU.  Subclasses
        /// don't need the header as its information is stored here.  In
        /// addition, this allows them to manipulate the response data
        /// all they want without destroying the original.  The copying is
        /// done every time this property is accessed, so use caution in a
        /// high-performance setting.
        /// </summary>
        protected byte[] ResponseAfterHeader
        {
            get
            {
                byte[] remainder = ByteArrayCopy(_Response,
                                                    HEADER_LENGTH, _Response.Length - HEADER_LENGTH);

                return remainder;
            }
        }

        /// <summary>
        /// Creates a response PDU.  This will also decode and store the
        /// header.  This will trim down the response so that it is the
        /// actual command size( such as if you are using a buffer), although
        /// if the length of the response is the same as the command length,
        /// it performs a pass-through to avoid a performance hit.
        /// </summary>
        /// <param name="response">The byte response from the SMSC.</param>
        protected SMPPResponse(byte[] response)
        {
            _TLVTable = new TLVTable();
            _Response = response;

            _CommandLength = DecodeCommandLength(response);

            _CommandID = (uint)DecodeCommandID(response);

            _CommandStatus = UnsignedNumConverter.SwapByteOrdering(
                                 BitConverter.ToUInt32(response, 8));

            _SequenceNumber = UnsignedNumConverter.SwapByteOrdering(
                                    BitConverter.ToUInt32(response, 12));

            _Response = TrimResponsePDU(response);
        }

        /// <summary>
        /// Utility method to allow the PDU factory to decode the command
        /// length without knowing about packet structure.  Some SMSCs combine
        /// response packets (even though they shouldn't).
        /// </summary>
        /// <param name="response">The PDU response packet.</param>
        /// <returns>The length of the PDU command.</returns>
        public static UInt32 DecodeCommandLength(byte[] response)
        {
            return UnsignedNumConverter.SwapByteOrdering(
                         BitConverter.ToUInt32(response, 0));
        }

        /// <summary>
        /// Utility method to allow the PDU factory to decode the command
        /// ID without knowing about packet structure.  Some SMSCs combine
        /// response packets (even though they shouldn't).
        /// </summary>
        /// <param name="response">The PDU response packet.</param>
        /// <returns>The ID of the PDU command (e.g. cancel_sm_resp).</returns>
        public static CommandIDType DecodeCommandID(byte[] response)
        {
            uint id = UnsignedNumConverter.SwapByteOrdering(
                            BitConverter.ToUInt32(response, 4));

            return (CommandIDType)Enum.Parse(typeof(CommandIDType),
                                                 Enum.GetName(typeof(CommandIDType), id));
        }

        /// <summary>
        /// Gets the optional parameter string associated with
        /// the given tag.
        /// </summary>
        /// <param name="tag">The tag in TLV.</param>
        /// <returns>The optional parameter string, the empty
        /// string if not found.</returns>
        public string GetOptionalParamString(UInt16 tag)
        {
            return _TLVTable.GetOptionalParamString(tag);
        }

        /// <summary>
        /// Gets the optional parameter bytes associated with
        /// the given tag.
        /// </summary>
        /// <param name="tag">The tag in TLV.</param>
        /// <returns>The optional parameter bytes, null if
        /// not found.</returns>
        public byte[] GetOptionalParamBytes(UInt16 tag)
        {
            return _TLVTable.GetOptionalParamBytes(tag);
        }

        /// <summary>
        /// Decodes the bind response from the SMSC.
        /// </summary>
        protected abstract void DecodeSMSCResponse();

        /// <summary>
        /// Trims the trailing zeroes off of the response PDU.  Useful for
        /// tracing and other purposes.  This uses the command length to
        /// actually trim it down, so TLVs and strings are not lost.  If the
        /// response actually is the same length as the command length, this
        /// method performs a pass-through.
        /// </summary>
        /// <returns>The trimmed PDU (byte array).</returns>
        public static byte[] TrimResponsePDU(byte[] response)
        {
            uint commLength = DecodeCommandLength(response);

            if (commLength == response.Length)
                return response;

            //trap any weird data coming in
            if (commLength >= Int32.MaxValue || commLength > response.Length)
                return new Byte[0];

            byte[] trimmed = new Byte[commLength];

            for (int i = 0; i < trimmed.Length; i++)
                trimmed[i] = response[i];

            return trimmed;
        }

        /// <summary>
        /// Copies part of the source array to the destination array.  The general
        /// algorithm is to copy from array[startIndex] to
        /// array[startIndex + length - 1].  I wrote this because Array.Copy
        /// didn't let me specify a starting point.
        /// </summary>
        /// <param name="source">The source array.</param>
        /// <param name="startIndex">The starting index.  Must be 0 or greater.</param>
        /// <param name="length">The length of the source to
        /// copy.  Must be 0 or greater.</param>
        /// <returns>The copied section of the byte array.</returns>
        public static byte[] ByteArrayCopy(byte[] source,
                                            int startIndex, int length)
        {
            byte[] destination = new byte[length];
            int m = 0;

            for (int i = startIndex; i < startIndex + length; i++)
            {
                destination[m] = source[i];
                m++;
            }

            return destination;
        }

        /// <summary>
        /// Copies from the source array, starting at startIndex to the end of the source
        /// array.  I wrote this because Array.Copy didn't let me specify a starting
        /// point.
        /// </summary>
        /// <param name="source">The source array.</param>
        /// <param name="startIndex">The starting index.  Must be 0 or greater.</param>
        /// <returns>The copied section of the byte array.</returns>
        public static byte[] ByteArrayCopy(byte[] source,
                                            long startIndex)
        {
            byte[] destination = new byte[source.Length - startIndex];
            long m = 0;

            for (long i = startIndex; i < source.Length; i++)
            {
                destination[m] = source[i];
                m++;
            }

            return destination;
        }

        /// <summary>
        /// Gets the next null-terminated string from the given byte array.  Note: this
        /// method also trims off the string (including the null character) from the
        /// byte array, so that it can be easily used in subsequent operations.
        /// This is equivalent to GetStringFromBody( ref remainder, 0 ).
        /// </summary>
        /// <param name="remainder">The byte array to
        /// pull the string from.  This should not
        /// contain extraneous data.</param>
        /// <returns>The string retrieved.</returns>
        public static string GetStringFromBody(ref byte[] remainder)
        {
            return GetStringFromBody(ref remainder, 0);
        }

        /// <summary>
        /// Gets the next null-terminated string from the given byte array.  Note: this
        /// method also trims off the string (including the null character) from the
        /// byte array, so that it can be easily used in subsequent operations.
        /// </summary>
        /// <param name="remainder">The byte array to
        /// pull the string from.  This should not
        /// contain extraneous data.</param>
        /// <param name="startIndex">The index to start getting the string from.
        /// Usually zero.</param>
        /// <returns>The string retrieved.</returns>
        public static string GetStringFromBody(ref byte[] remainder,
                                                int startIndex)
        {
            int i;

            //find where the null character (end of string) is
            //this stops ON the null character

            for (i = startIndex; i < remainder.Length &&
                    remainder[i] != 0x00; i++)
                ;

            return GetStringFromBody(ref remainder, startIndex, i);
        }

        /// <summary>
        /// Gets the next string from the given byte array.  Note: this
        /// method also trims off the string from the
        /// byte array, so that it can be easily used in subsequent operations.
        /// </summary>
        /// <param name="remainder">The byte array to pull the string from.
        /// This should not contain extraneous data.</param>
        /// <param name="startIndex">The index to start getting the string from.
        /// Usually zero.</param>
        /// <param name="endIndex">The index to end at.  The character at
        /// this index will not be included.</param>
        /// <returns>The string retrieved.</returns>
        public static string GetStringFromBody(ref byte[] remainder,
                                                int startIndex, int endIndex)
        {
            string result;

            //make sure we aren't trying to read non-command
            //data
            if (endIndex <= remainder.Length)
                result = System.Text.Encoding.ASCII.GetString(
                             remainder, startIndex, endIndex);
            else
                result = "";

            //now trim down the remainder-go one past the null
            //character and reduce the length by one as well
            if (endIndex < remainder.Length)
            {
                remainder = ByteArrayCopy(remainder, endIndex + 1,
                    remainder.Length - endIndex - 1);
            }
            else
            {
                remainder = new byte[0];
            }

            return result;
        }
    }
}
